package org.jeecg.modules.util;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import icu.mhb.mybatisplus.plugln.core.JoinLambdaWrapper;
import lombok.extern.slf4j.Slf4j;
import ncme.digital.hszy.tb.ThingsBoardTemplate;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.ibatis.reflection.property.PropertyNamer;
import org.apache.shiro.SecurityUtils;
import org.dromara.streamquery.stream.core.stream.Steam;
import org.dromara.streamquery.stream.plugin.mybatisplus.Many;
import org.dromara.streamquery.stream.plugin.mybatisplus.One;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.base.entity.NCMEExcelExportView;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.modules.api.common.entity.ITbDeviceAlarm;
import org.jeecg.modules.service.IFileService;
import org.jeecg.modules.system.entity.SysRole;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.entity.SysUserRole;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;

@Slf4j
@Component
public class ServiceUtils {
    public static final String LIKE_SYMBOL = "*";
    private static final String TB_TIMESERIES_KEY_VALUE = "value";
    private static final String TB_TIMESERIES_KEY_TS = "ts";

    @Resource
    private JeecgBaseConfig jeecgBaseConfig;
    @Autowired
    private IFileService fileService;

    /**
     * 获取当前用户的角色编码
     *
     * @param loginUser 传空则自动获取
     * @return
     */
    public static List<String> getRoleCodes(LoginUser loginUser) {
        if (loginUser == null)
            loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (loginUser == null)
            return Collections.emptyList();
        String roleIds = loginUser.getRoleIds();
        List<String> roleIdList;
        if (StringUtils.isBlank(roleIds)) {
            // 从用户角色关系表中重新查取
            String username = loginUser.getUsername();
            if (StringUtils.isBlank(username)) {
                return Collections.emptyList();
            }
            String userId = One.of(SysUser::getUsername).eq(username).value(SysUser::getId).query(false);
            if (userId == null) {
                return Collections.emptyList();
            }
            roleIdList = Many.of(SysUserRole::getUserId).eq(userId).value(SysUserRole::getRoleId).query();

        } else
            roleIdList = Arrays.asList(roleIds.split(","));
        return Steam.of(roleIdList).map(roleId -> One.of(SysRole::getId).eq(roleId).value(SysRole::getRoleCode).query(false)).toList();
    }

    /**
     * 在指定的字段上添加模糊搜索符号
     *
     * @param entity
     * @param fields
     * @param <E>
     */
    @SafeVarargs
    public static <E> void convertLike(E entity, SFunction<E, ?>... fields) {
        Arrays.stream(fields).forEach(fieldFunc -> {
            try {
                String fieldName = PropertyNamer.methodToProperty(LambdaUtils.extract(fieldFunc).getImplMethodName());
                Field field = getField(entity, fieldName);
                if (field.getType().equals(String.class)) {
                    field.setAccessible(true);
                    if (field.get(entity) != null) {
                        String originValue = field.get(entity).toString();
                        field.set(entity, LIKE_SYMBOL + originValue + LIKE_SYMBOL);
                    }
                    field.setAccessible(false);
                }
            } catch (Exception e) {
                log.error("模糊搜索异常", e);
            }
        });
    }

    /**
     * 根据用户realname模糊查询
     *
     * @param joinLambdaWrapper
     * @param request
     * @param userNameFields    需要模糊查询用户名的字段
     * @param <T>
     * @param <F>
     */
    @SafeVarargs
    public static <T, F> void addQueryByUserName(JoinLambdaWrapper<T> joinLambdaWrapper, HttpServletRequest request, SFunction<F, Object>... userNameFields) {
        if (request == null) return;
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (MapUtils.isEmpty(parameterMap)) return;
        for (SFunction<F, Object> nameField : userNameFields) {
            String fieldName = PropertyNamer.methodToProperty(LambdaUtils.extract(nameField).getImplMethodName());
            String key = fieldName + "_dictText";
            if (parameterMap.containsKey(key)) {
                String userDictText = parameterMap.get(key)[0];
                if (StringUtils.isNotBlank(userDictText))
                    joinLambdaWrapper.leftJoin(SysUser.class, SysUser::getUsername, nameField, fieldName).like(SysUser::getRealname, userDictText).end();
            }
        }
    }

    public <E> ModelAndView exportXls(HttpServletRequest request, E entity, IGetWrapperService<E> service, String title) {
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();

        // 获取要导出的数据
        JoinLambdaWrapper<E> listWrapper = service.createListWrapper(entity, request);
        // 适配jeecg前端
        String selections = request.getParameter("selections");
        if (oConvertUtils.isNotEmpty(selections)) {
            selections = Steam.of(selections.split(",")).map(s -> "'" + s + "'").join(",");
            listWrapper.apply("id in (" + selections + ")");
        }
        List<E> exportList = listWrapper.joinList();

        // Step.3 AutoPoi 导出Excel
        return createModelAndView(entity.getClass(), title, sysUser, exportList);
    }

    public <E extends IPkGetter, P extends ISubListSetter<S>, S> ModelAndView exportXlsWithSub(HttpServletRequest request, E entity, IGetWrapperService<E> service, Class<P> pageVOClz, SFunction<String, List<S>> subListGetter, String title) {
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();

        // 获取导出数据
        JoinLambdaWrapper<E> listWrapper = service.createListWrapper(entity, request);
        // 适配jeecg前端
        String selections = request.getParameter("selections");
        if (oConvertUtils.isNotEmpty(selections)) {
            selections = Steam.of(selections.split(",")).map(s -> "'" + s + "'").join(",");
            listWrapper.apply("id in (" + selections + ")");
        }
        List<E> exportList = listWrapper.joinList();

        // 组装pageList
        List<P> pageList = new ArrayList<>();
        try {
            for (E main : exportList) {
                P vo = pageVOClz.newInstance();
                BeanUtils.copyProperties(main, vo);
                List<S> subList = subListGetter.apply(main.getId());
                vo.setSubList(subList);
                pageList.add(vo);
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        // AutoPoi 导出Excel
        return createModelAndView(pageVOClz,title,sysUser,pageList);
    }

    public void deleteUselessFile(@NotNull Collection<String> filePathListOld, @NotNull Collection<String> filePathListNew){
        filePathListOld=new ArrayList<>(filePathListOld);
        filePathListOld.removeAll(filePathListNew);
        for (String path : filePathListOld) {
            fileService.deleteFileByRelativePath(path);
        }
    }

    /**
     * 将JSONObject中的时间字段转换成Date，如果是时间戳为0，则返回null
     * @param jsonObject
     * @param tsKey
     * @return
     */
    public static Date getDateFromJSONObject(@NotNull JSONObject jsonObject, @NotNull String tsKey) {
        Object dateO = jsonObject.get(tsKey);
        if (dateO == null)
            return null;
        Long dateL = jsonObject.getLong(tsKey);
        if (Long.valueOf(0).equals(dateL))
            return null;
        return jsonObject.getDate(tsKey);
    }

    /**
     * 从thingsboard的时序数据中获取报警时刻的物联值
     * @param tbDeviceAlarm
     * @param thingsBoardTemplate
     * @return
     */
    public static @Nullable String getAlarmValueFromTimeseriesData(ITbDeviceAlarm tbDeviceAlarm, ThingsBoardTemplate thingsBoardTemplate) {
        String alarmValue = null;
        for (int i = 0, maxTry = 3600 / 20 * 24 * 365; i < maxTry && alarmValue == null; i++) {
            Date alarmTime = tbDeviceAlarm.getAlarmTime();
            Date startTime = DateUtils.addSeconds(alarmTime, -10 * i);
            Date endTime = DateUtils.addSeconds(alarmTime, 20 * i);
            try {
                JSONObject timeseries = thingsBoardTemplate.getTelemetryApi().getTimeseries("DEVICE", tbDeviceAlarm.getDeviceIdTb(), TB_TIMESERIES_KEY_VALUE, startTime.getTime(), endTime.getTime(), null, 1, "AVG", "ASC", true);
                JSONArray tsArray = timeseries.getJSONArray(TB_TIMESERIES_KEY_VALUE);
                if (!tsArray.isEmpty()) {
                    if (tsArray.size() == 1)
                        alarmValue = tsArray.getJSONObject(0).getString(TB_TIMESERIES_KEY_VALUE);
                    else {
                        Optional<Object> reduce = Steam.of(tsArray).reduce((a, b) -> {
                            long tsA = ((JSONObject) a).getLongValue(TB_TIMESERIES_KEY_TS);
                            long tsB = ((JSONObject) b).getLongValue(TB_TIMESERIES_KEY_TS);
                            long ts = alarmTime.getTime();
                            // 返回tsA与ts的差和tsB与ts的差中绝对值最小的那个对象a或者b
                            return Math.abs(tsA - ts) < Math.abs(tsB - ts) ? a : b;
                        });
                        if (reduce.isPresent())
                            alarmValue = ((JSONObject) reduce.get()).getString(TB_TIMESERIES_KEY_VALUE);
                    }
                }
            } catch (Exception ignored) {
            }
        }
        if (alarmValue == null)
            alarmValue = tbDeviceAlarm.getAlarmValue();
        return alarmValue;
    }

    private @NotNull ModelAndView createModelAndView(Class<?> entityClz, String title, LoginUser sysUser, List<?> exportList) {
        ModelAndView mv = new ModelAndView(new NCMEExcelExportView());
        // 此处设置的filename无效 ,前端会重更新设置一下
        mv.addObject(NormalExcelConstants.FILE_NAME, title);
        mv.addObject(NormalExcelConstants.CLASS, entityClz);
        //update-begin--Author:liusq  Date:20210126 for：图片导出报错，ImageBasePath未设置--------------------
        ExportParams exportParams = new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
        exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
        //update-end--Author:liusq  Date:20210126 for：图片导出报错，ImageBasePath未设置----------------------
        mv.addObject(NormalExcelConstants.PARAMS, exportParams);
        mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
        return mv;
    }

    private static <E> @NotNull Field getField(E entity, String fieldName) {
        Field field = null;
        Class<?> clazz = entity.getClass();
        do {
            try {
                field = clazz.getDeclaredField(fieldName);
            } catch (Exception e) {
                clazz = clazz.getSuperclass();   //没有的话找父类
            }
        } while (field == null && clazz != null);
        if (field == null)
            throw new JeecgBootException(String.format("字段[%s]不存在，无法模糊搜索", fieldName));
        return field;
    }
}
